"""Singleton classes.

Inherit one of those classes to create singletonized objects.

** You cannot instanciate one of those without sublclassing and defining
the __init__method.

** A not so obvious restriction exists, saying that the __init__ of a KeySingleton
cannot use variable argument nor variable keyword arguments as this would lead
to undistinguishability of two instanciations.
A SyntaxError is raise if you try.
You can turn off this check if you really understand the resulting behavior by
setting:
    ALLOW_VARARGS = True
    
# LICENSE ....................................................................
    This file is part of DataK.

    DataK is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    DataK is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with DataK.  If not, see <http://www.gnu.org/licenses/>.
# ............................................................................

"""

import weakref
import inspect

ALLOW_VARARGS = False   # to see if we must raise when subclasses use varags
                        # or varkwargs in __init__
#
#--- KeySingleton
#
class KeySingleton( object ):
    '''
    The KeySingleton class holds weak references to its instances
    in order to make them singletons, based on the hashable object
    returned by _SingleKey.

    The arguments passed to _SingleKey are the one received by the
    constructor. Thus, subclass must only use hashable objects in their
    constructor arguments or override the _SingleKey method.
    
    Weak reference are used, the instances will be deleted when not
    in use anymore.

    Every instances of subclasses are stored in KeySingleton._singles,
    not in subclasses. So you can singletonize across several classes.
    Note that it can become strange for the coder to instanciate a class
    and receive another.
    Derive ClassKeySingleton if you don't need this as a default behavior.
    
    '''
    # weak refs to the singleton instances
    _singles = weakref.WeakValueDictionary()

    @staticmethod
    def _GetInitArgs( klass, *args, **kwargs ):
        '''
        COnstruct a tuple representing the actual values used by the
        __init__ of klass if you would give it *args and **kwargs.
        That is, resolve default values and the argument order.
        
        This is needed to call _SingleKey with the same arguments
        when the user types:
            MyClass( 1, 2 )
            MyClass( 1, b=2 )
            MyClass( a=1, b=2 )
            MyClass( b=2, a=1 )
            etc...
            
        '''
        # TODO: check the cost of all this...
        
        # get the __init__ signature
        argnames, varargs, varkw, defaults = inspect.getargspec( klass.__init__ )
        # the method is bound so we must pop the first arg 'self'
        if argnames.pop(0) != 'self':
            raise SyntaxError(
                "in class "+str(klass)+", __init__ first arg is not 'self' !"
            )

        # compute default values as dict
        kwdefaults = {}
        if defaults:
            kwdefaults.update( zip( argnames[-len(defaults):], defaults ) )
                
        # get given positional values first
        ret = list(args)

        # add kwarg value in positional order, getting default if needed
        nbargs = len(args)
        kwnames = argnames[nbargs:]
        # with python 2.5:
        # ret.extend([ 
        #     kwargs[kwname] if kwname in kwargs else kwdefaults[kwname] 
        #     for kwname in kwnames
        # ])
        for kwname in kwnames:
            if kwname in kwargs:
                ret.append( kwargs[kwname] )
            else:
                ret.append( kwdefaults[kwname] )
        
        # if the __init__ accepts varargs or varkw:
        # TODO: I so dont know how to handle this :(
        if not ALLOW_VARARGS:
            if varargs is not None:
                raise SyntaxError(
                    "Not allowed varargs found in "+klass.__name__+".__init__"
                )
            if varkw is not None:
                raise SyntaxError(
                    "Not allowed varkw found in "+klass.__name__+".__init__"
                )

        # return the complete list of positional values
        return tuple(ret)
        
    @classmethod
    def _SingleKey( klass, *args ):
        '''
        Return the key used to singletonize instances.
        The arguments received are the one sent to the constructor.
        (with keyword args resolved in args)
        
        Subclasses can override this to configure what is unic.
        Retruning None means don't singletonize.
        
        Default behavior is to singletonize instances having the same
        arguments passed to the constructor.
        (/!\ The class is not part of the key! see ClassKeySingleton for that)
        
        The key is stored in memory as long as the singleton is alive,
        you should avoid using heavy objects in the constructor arguments.
        
        '''
        return args

    def __new__( klass, *args, **kwargs ):
        '''
        Return the singleton instance, creating it if needed.
        '''
        key = klass._SingleKey( *KeySingleton._GetInitArgs( klass, *args, **kwargs ) )
        #print '###### NEW', klass, 'key:', args, kwargs, key
        if key is None:
            # no key no singleton
            return object.__new__( klass )
            
        if key in KeySingleton._singles:
            # use existing instance
            return KeySingleton._singles[key]
        
        # create, store and return a new instance    
        o = object.__new__( klass )
        klass._singles[key] = o
        return o

        
#
#--- ClassKeySingleton
#
class ClassKeySingleton( KeySingleton ):
    '''
    This KeySingleton use the object class in addition to constructor
    args to singeltonize instances.
    '''
    @classmethod
    def _SingleKey( klass, *args ):
        '''
        Singletonize instances on (klass, cnt, id)
        '''
        return klass, args





